// OpentTxl-C Version 11 predefined built-in functions
// J.R. Cordy, Jan 2023

// Copyright 2023, James R. Cordy and others

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
// and associated documentation files (the “Software”), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies 
// or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
// AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// Modification Log

// v11.0 Initial revision, adapted from OpenTxl 11.0

// v11.1 Added new predifined function [faccess]

// v11.2 Added new shallow extract [^/]

// TXL predefined built-in functions

// I/O, strings, memory allocation
#include "support.h"

// Global modules
#include "locale.h"
#include "limits.h"
#include "options.h"
#include "tokens.h"
#include "errors.h"
#include "trees.h"
#include "shared.h"
#include "charset.h"
#include "idents.h"
#include "symbols.h"
#include "rules.h"

// Phase dependencies
#include "scan.h"
#include "parse.h"
#include "xform.h"
#include "unparse.h"

// Check interface consistency
#include "predefs.h"

// Line-oriented Fget/Fput file to stream mappings
#define maxFgetputFiles 10
struct predefs_fgetputFilesT {
    tokenT name;
    int stream;
};
// 1-origin [1 .. maxFgetputFiles]
static struct predefs_fgetputFilesT predefs_fgetputFiles[maxFgetputFiles + 1];

// Garbage collection utility - predefined functions [parse] and [reparse]
// cannot recover from running out of space, so we pre-check we have 10% 
// of the treespace left before running them

static void predefs_checkSufficientParseSpace (void) {
    int nineTenthsMaxTrees = 9 * (maxTrees / 10);
    int nineTenthsMaxKids = 9 * (maxKids / 10);

    if ((tree_treeCount > nineTenthsMaxTrees) || (tree_kidCount > nineTenthsMaxKids)) {
        // Force a garbage recovery
        if (options_option[verbose_p]) {
            if (tree_treeCount > nineTenthsMaxTrees) {
                error ("", outOfTreesMessage, DEFERRED, 521);
            } else {
                error ("", outOfKidsMessage, DEFERRED, 522);
            }
        }
        throw (OUTOFTREES);
    }
}

// Long string temporaries - all strings in applyPredefinedFunction use these
// rather than string variables in order to avoid large stack space in rule applications 

// Must be [maxLineLength + maxStringLength + 1] for string type cheats

static longstring predefs_LS1;
static longstring predefs_LS2;
static longstring predefs_LS3;

// Routines to evaluate and de-evaluate string tokens

// An "evaluated" string is the text of the actual meaning of the quoted string - for example,
// evaluateString ('"some\ttab\ttext\n"') == 'someTABtabTABtextNL',
//     where TAB and NL mean the actual ASCII character codes.
// unevaluateString ('someTABtabTABtextNL') reverses the evaluation, 
//     quoting and encoding the text.
// Both routines simply return the original tokentext if the token kind is not treeKind_stringlit or treeKind_charlit.

static void predefs_evaluateString (const longstring tokentext, const enum treeKindT kind, longstring text)
{
    // Always long enough when called, even if tokentext is a longstring
    lstringcpy (text, tokentext);

    if ((kind == treeKind_stringlit) || (kind == treeKind_charlit)) {
        // Strip off quotes
        assert (lstringlen (tokentext) >= 2);
        lsubstring (text, tokentext, 2, (lstringlen (tokentext) - 1));

        // Unescape embedded escaped quotes if necessary
        if (charset_stringlitEscapeChar != ' ') {
            unsigned char escapeChar, quoteChar;
            if (kind == treeKind_stringlit) {
                escapeChar = charset_stringlitEscapeChar;
                quoteChar = '"';
            } else {
                escapeChar = charset_charlitEscapeChar;
                quoteChar = '\'';
            }

            int ix = 1;
            int len = lstringlen (text);
            while (true) {
                if (ix > len) break;

                if ((lstringchar (text, ix) == escapeChar) && (ix < len) && (lstringchar (text, ix + 1) == quoteChar)) {
                    // shift string left over escape char
                    for (int i = ix + 1; i <= len + 1; i++) {
                        lstringchar (text, i - 1) = lstringchar (text, i);
                    }
                    len -= 1;
                }
                ix += 1;
            }
        }
    }
}

static void predefs_unevaluateString (const enum treeKindT kind, longstring text)
{
    if ((kind == treeKind_stringlit) || (kind == treeKind_charlit)) {
        assert (lstringlen (text) <= maxLineLength - 2);
        unsigned char escapeChar, quoteChar;
        if (kind == treeKind_stringlit) {
            escapeChar = charset_stringlitEscapeChar;
            quoteChar = '"';
        } else {
            escapeChar = charset_charlitEscapeChar;
            quoteChar = '\'';
        }

        // Escape embedded quotes if necessary
        if (charset_stringlitEscapeChar != ' ') {
            int ix = 1;
            int len = lstringlen (text);
            while (true) {
                if (len == (maxLineLength - 2)) break;
                if (ix > len) break;

                if (text[ix - 1] == quoteChar) {
                    // shift right to make room for escape char
                    for (int i = len + 1; i >= ix; i--) {  // (sic!)
                        text[(i + 1) - 1] = text[i - 1];
                    }
                    text[ix - 1] = escapeChar;
                    len += 1;
                    ix += 1;
                }
                ix += 1;
            }
        }

        // Add surrounding quotes
        int len = lstringlen (text);
        // shift right to make room for leading quote
        for (int i = len + 1; i >= 1; i--) {  // (sic!)
            lstringchar (text, i + 1) = lstringchar (text, i);
        }
        lstringchar (text, 1) = quoteChar;
        lstringchar (text, len + 2) = quoteChar;
        lstringchar (text, len + 3) = EOS;
    }
}

// Predefined function error procedure 

static void predefs_predefinedError (const string message, const tokenT rulename, const tokenT callername)
{
    string *context = (string *) &predefs_LS3;
    stringprintf (*context, "predefined function [%s], called from [%s]", *ident_idents[rulename], *ident_idents[callername]);
    error (*context, message, FATAL, 592);
}

// Utility routines to optimize stack use

static void predefs_convertHexToString (const double hex, string s)
{
    intstring (round (hex), 0, 16, s);
}

static void predefs_convertRealToString (const double r, string s)
{
    realstring (r, 0, s);
}

// The TXL predefined functions

void predefs_applyPredefinedFunction (const int ruleIndex, const struct transformer_ruleEnvironmentT *ruleEnvironment, 
    const treePT originalTP, treePT *resultTP, bool *matched)
{
    *resultTP = originalTP;

    switch (ruleIndex) {

        case spliceR:
            {
                // Generic repeat splice or append 
                // Repeat1 [. Repeat2]  or  Repeat1 [. Element2] 

                // can't fail since types are ok
                *matched = true;

                treePT scopeTP = *resultTP;
                treePT paramTP = transformer_valueTP[ruleEnvironment->valuesBase + 1];

                // get the element type of the repeat 
                assert (stringncmp (ident_idents[tree_trees[scopeTP].name], "repeat_0_", 9) == 0); 
                string *X = (string *) &(lstringchar (*ident_idents[tree_trees[scopeTP].name], 10));
                tokenT XT = ident_lookup (*X);
                assert (XT != NOT_FOUND);
                tokenT repeatXT = tree_trees[scopeTP].name;

                // build the tail of the spliced repeat 
                if (tree_trees[paramTP].name == repeatXT) {
                    // repeat_0_X
                    if (tree_trees[tree_kid1TP (paramTP)].kind == treeKind_empty) {
                        // splicing an empty repeat - nothing to do!   
                        return;
                    }

                    // a nonempty repeat - get the repeat_1_X node 
                    // must copy since this ruleEnvironment.valueTP becomes part of the result
                    // unless the calling rule has only one reference to it, in which case there is no problem 
                    if (ruleEnvironment->parentrefs != 1) {
                        treePT paramCopyTP;
                        tree_copyTree (paramTP, &(paramCopyTP));
                        paramTP = paramCopyTP;
                    }

                } else {
                    // splicing X - need to make into a repeat_0_X
                    assert ((tree_trees[paramTP].name == XT) || (kindType[tree_trees[paramTP].kind] == XT));

                    // must copy since this ruleEnvironment.valueTP becomes part of the result
                    // unless the calling rule has only one reference to it, in which case there is no problem 
                    treePT paramCopyTP = paramTP;
                    if (ruleEnvironment->parentrefs > 1) {
                        tree_copyTree (paramTP, &(paramCopyTP));
                    }

                    // paramTP must be a new repeat_0_X
                    paramTP = tree_newTreeInit (treeKind_repeat, repeatXT, repeatXT, 0, nilKid);

                    // every repeat is ended with a doubly empty repeat
                    treePT endTP = tree_newTreeInit (treeKind_repeat, repeatXT, repeatXT, 0, nilKid);
                    tree_makeTwoKids (endTP, emptyTP, emptyTP);

                    // now construct the whole thing
                    tree_makeTwoKids (paramTP, paramCopyTP, endTP);
                }

                // hopefully it is a repeat_0_X tree we are attaching 
                assert (tree_trees[paramTP].name == repeatXT);

                // now attach the tail to the scope repeat
                assert (tree_trees[scopeTP].name == repeatXT);

                // attaching to a repeat_0_X - it may be empty! 
                if (tree_trees[tree_kid1TP (scopeTP)].kind == treeKind_empty) {
                    // empty repeat - replace it with the nonempty tail
                    tree_setKids (scopeTP, tree_trees[paramTP].kidsKP);
                    return;
                }

                // we are now attaching to a nonempty repeat 
                assert (tree_trees[scopeTP].name == repeatXT);

                // find last "repeat_0_" tree 
                while (true) {
                    if (tree_trees[tree_kid2TP (scopeTP)].kind == treeKind_empty) break;
                    // go on to the next "repeat_0_" tree
                    scopeTP = tree_kid2TP (scopeTP);
                }

                // now we have an empty repeat_0_X to attach to 
                assert (tree_trees[scopeTP].name == repeatXT);

                // and attach the tail to it 
                tree_setKids (scopeTP, tree_trees[paramTP].kidsKP);
            }
            break;

        case listSpliceR:
            {
                // Generic list splice or append 
                // List1 [. List2]  or  List1 [. Element2] 

                // can't fail since types are ok
                *matched = true;

                treePT scopeTP = *resultTP;
                treePT paramTP = transformer_valueTP[ruleEnvironment->valuesBase + 1];

                // get the element type of the list 
                assert (stringncmp (ident_idents[tree_trees[scopeTP].name], "list_0_", 7) == 0); 
                string *X = (string *) &(lstringchar (*ident_idents[tree_trees[scopeTP].name], 8));
                tokenT XT = ident_lookup (*X);
                assert (XT != NOT_FOUND);
                tokenT listXT = tree_trees[scopeTP].name;

                // build the tail of the spliced list 
                if (tree_trees[paramTP].name == listXT) {
                    // list_0_X
                    if (tree_trees[tree_kid1TP (paramTP)].kind == treeKind_empty) {
                        // splicing an empty list - nothing to do!   
                        return;
                    }

                    // a nonempty list - get the list_1_X node 
                    // must copy since this ruleEnvironment.valueTP becomes part of the result
                    // unless the calling rule has only one reference to it, in which case there is no problem 
                    if (ruleEnvironment->parentrefs != 1) {
                        treePT paramCopyTP;
                        tree_copyTree (paramTP, &(paramCopyTP));
                        paramTP = paramCopyTP;
                    }

                } else {
                    // splicing X - need to make into a list_0_X
                    assert ((tree_trees[paramTP].name == XT) || (kindType[tree_trees[paramTP].kind] == XT));

                    // must copy since this ruleEnvironment.valueTP becomes part of the result
                    // unless the calling rule has only one reference to it, in which case there is no problem 
                    treePT paramCopyTP = paramTP;
                    if (ruleEnvironment->parentrefs > 1) {
                        tree_copyTree (paramTP, &(paramCopyTP));
                    }

                    // paramTP must be a new list_0_X
                    paramTP = tree_newTreeInit (treeKind_list, listXT, listXT, 0, nilKid);

                    // every list is ended with a doubly empty list
                    treePT endTP = tree_newTreeInit (treeKind_list, listXT, listXT, 0, nilKid);
                    tree_makeTwoKids (endTP, emptyTP, emptyTP);

                    // now construct the whole thing
                    tree_makeTwoKids (paramTP, paramCopyTP, endTP);
                }

                // hopefully it is a list_0_X tree we are attaching 
                assert (tree_trees[paramTP].name == listXT);

                // now attach the tail to the scope list
                assert (tree_trees[scopeTP].name == listXT);

                // attaching to a list_0_X - it may be empty! 
                if (tree_trees[tree_kid1TP (scopeTP)].kind == treeKind_empty) {
                    // empty list - replace it with the nonempty tail
                    tree_setKids (scopeTP, tree_trees[paramTP].kidsKP);
                    return;
                }

                // we are now attaching to a nonempty list 
                assert (tree_trees[scopeTP].name == listXT);

                // find last "list_0_" tree 
                while (true) {
                    if (tree_trees[tree_kid2TP (scopeTP)].kind == treeKind_empty) break;
                    // go on to the next "list_0_" tree
                    scopeTP = tree_kid2TP (scopeTP);
                }

                // now we have an empty list_0_X to attach to 
                assert (tree_trees[scopeTP].name == listXT);

                // and attach the tail to it 
                tree_setKids (scopeTP, tree_trees[paramTP].kidsKP);
            }
            break;

        case addR: case subtractR: case multiplyR: case divideR: case divR: case remR:
            {
                // N1 [+ N2]    N1 := N1 + N2
                // N1 [- N2]    N1 := N1 - N2
                // N1 [* N2]    N1 := N1 * N2
                // N1 [/ N2]    N1 := N1 / N2
                // N1 [div N2]  N1 := N1 div N2
                // N1 [rem N2]  N1 := N1 rem N2

                // can't fail since types are ok
                *matched = true;

                enum treeKindT kind = treeKind_undefined;

                switch (tree_trees[*resultTP].kind) {
                    case treeKind_number: case treeKind_floatnumber: case treeKind_decimalnumber: case treeKind_integernumber:
                        kind = treeKind_number;
                        break;
                    case treeKind_id: case treeKind_upperlowerid: case treeKind_upperid: case treeKind_lowerupperid: case treeKind_lowerid:
                        kind = treeKind_id;
                        break;
                    default :
                        kind = tree_trees[*resultTP].kind;
                        break;
                }

                treePT oldresultTP = *resultTP;
                *resultTP = tree_newTreeClone (oldresultTP);

                if (kind == treeKind_number) {
                    double N1 = stringreal((*ident_idents[tree_trees[*resultTP].name]));
                    double N2 = stringreal((*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name]));
                    switch (ruleIndex) {
                        case addR:
                            N1 += N2;
                            break;
                        case subtractR:
                            N1 -= N2;
                            break;
                        case multiplyR:
                            N1 *= N2;
                            break;
                        case divideR:
                            {
                                if (N2 != 0) {
                                    N1 /= N2;
                                } else {
                                    predefs_predefinedError ("Division by zero", 
                                        transformer_applyingRuleName, transformer_callingRuleName);
                                }
                            }
                            break;
                        case divR:
                            {
                                int I1 = round ((double) N1);
                                int I2 = round ((double) N2);
                                if (I2 != 0) {
                                    I1 = I1 / I2;
                                } else {
                                    predefs_predefinedError ("Division by zero", 
                                        transformer_applyingRuleName, transformer_callingRuleName);
                                }
                                N1 = I1;
                            }
                            break;
                        case remR:
                            {
                                int I1 = round ((double) N1);
                                int I2 = round ((double) N2);
                                if (I2 != 0) {
                                    I1 = I1 % I2;
                                } else {
                                    predefs_predefinedError ("Division by zero", 
                                        transformer_applyingRuleName, transformer_callingRuleName);
                                }
                                N1 = I1;
                            }
                            break;
                    }

                    longstring *resultValue = &predefs_LS1;
                    longstring *rawNameString = (longstring *) (ident_idents[tree_trees[*resultTP].rawname]);

                    if ((lstringchar (*rawNameString, 1) == '0') && ((lstringchar (*rawNameString, 2) == 'x') || (lstringchar (*rawNameString, 2) == 'X'))) {
                        // octal or hex number
                        lsubstring (*resultValue, *rawNameString, 1, 2);
                        longstring *resultValue3 = (longstring *) &((*resultValue)[(2)]);
                        predefs_convertHexToString (N1, *resultValue3);
                    } else {
                        predefs_convertRealToString (N1, *resultValue);
                    }

                    tokenT resultT = ident_install (*resultValue, treeKind_number);
                    tree_setName (*resultTP, resultT);
                    tree_setRawName (*resultTP, resultT);

                } else {
                    // Token text concatenate 
                    assert ((kind > firstLeafKind) && (kind <= lastLeafKind));

                    // Concatenate the raw names in case they are different
                    longstring *S1 = &predefs_LS1;
                    longstring *S2 = &predefs_LS2;
                    predefs_evaluateString (*ident_idents[tree_trees[*resultTP].rawname], 
                        tree_trees[*resultTP].kind, *S1);
                    predefs_evaluateString (
                        *ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].rawname], 
                        tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *S2);

                    assert (ruleIndex == addR);

                    lstringcat (*S1, *S2);
                    predefs_unevaluateString (kind, *S1);
                    tokenT resultT = ident_install (*S1, kind);
                    tree_setRawName (*resultTP, resultT);

                    // If -case is on, normalize the internal name, otherwise it is the same
                    if (((options_option[case_p]) && (kind != treeKind_stringlit)) && (kind != treeKind_charlit)) {
                        lstringtolower (*S1);
                        resultT = ident_install (*S1, kind);
                        tree_setName (*resultTP, resultT);
                    } else {
                        tree_setName (*resultTP, resultT);
                    }
                }
            }
            break;

        case substringR:
            {
                // can't fail since types are right
                *matched = true;

                // Substring the raw name in case it is different
                longstring *S1 = &predefs_LS1;
                longstring *S2 = &predefs_LS2;
                predefs_evaluateString (*ident_idents[tree_trees[*resultTP].rawname], 
                    tree_trees[*resultTP].kind, *S1);

                int N1 = round (stringreal((*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name])));
                if (N1 > (stringlen (*S1) + 1)) {
                    N1 = stringlen (*S1) + 1;
                } else if (N1 < 1) {
                    N1 = 1;
                }

                int N2 = round (stringreal((*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 2]].name])));
                if (N2 > stringlen (*S1)) {
                    N2 = stringlen (*S1);
                } else if (N2 < (N1 - 1)) {
                    N2 = N1 - 1;
                }

                treePT oldresultTP = *resultTP;
                *resultTP = tree_newTreeClone (oldresultTP);

                lsubstring (*S2, *S1, N1, N2);
                predefs_unevaluateString (tree_trees[*resultTP].kind, *S2);
                tokenT resultT = ident_install (*S2, tree_trees[*resultTP].kind);
                tree_setRawName (*resultTP, resultT);

                // If -case is on, normalize the internal name, otherwise it is the same
                if ((options_option[case_p]) && (tree_trees[*resultTP].kind != treeKind_stringlit) 
                        && (tree_trees[*resultTP].kind != treeKind_charlit)) {
                    lstringtolower (*S2);
                    resultT = ident_install (*S2, tree_trees[*resultTP].kind);
                    tree_setName (*resultTP, resultT);
                } else {
                    tree_setName (*resultTP, resultT);
                }
            }
            break;

        case lengthR:
            {
                // can't fail since types are right
                *matched = true;

                treePT oldresultTP = *resultTP;
                *resultTP = tree_newTreeClone (oldresultTP);

                longstring *S1 = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *S1);

                int L = lstringlen (*S1);
                stringprintf (*S1, "%d", L);

                tokenT resultT = ident_install (*S1, treeKind_number);
                tree_setName (*resultTP, resultT);
                tree_setRawName (*resultTP, resultT);
            }
            break;

        case equalR: case notEqualR:
            {
                // N1 [= N2]    N1 = N2
                // N1 [~= N2]   N1 ~= N2
                // (defined as equality on numbers and text tokens, and identity on all other types)

                switch (tree_trees[*resultTP].kind) {
                    case treeKind_number: case treeKind_floatnumber: case treeKind_decimalnumber: case treeKind_integernumber:
                        {
                            double N1 = stringreal((*ident_idents[tree_trees[*resultTP].name]));
                            double N2 = stringreal((*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name]));
                            *matched = N1 == N2;
                        }
                        break;
                    default :
                        {
                            if ((tree_trees[*resultTP].kind > firstLeafKind) && (tree_trees[*resultTP].kind <= lastLeafKind)) {
                                longstring *S1 = &predefs_LS1;
                                longstring *S2 = &predefs_LS2;
                                predefs_evaluateString (*ident_idents[tree_trees[*resultTP].name], 
                                    tree_trees[*resultTP].kind, *S1);
                                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *S2);
                                *matched = lstringcmp (*S1, *S2) == 0;
                            } else {
                                *matched = tree_sameTrees (*resultTP, transformer_valueTP[ruleEnvironment->valuesBase + 1]);
                            }
                        }
                        break;
                }

                if (ruleIndex == notEqualR) {
                    *matched = !*matched;
                }
            }
            break;

        case lessR: case lessEqualR: case greaterR: case greaterEqualR :
            // N1 [> N2]        N1 > N2
            // N1 [< N2]        N1 < N2
            // N1 [<= N2]       N1 <= N2
            // N1 [>= N2]       N1 >= N2
            // (all of above also defined on strings and ids)
            {
                enum treeKindT kind = tree_trees[*resultTP].kind;

                switch (tree_trees[*resultTP].kind) {
                    case treeKind_number: case treeKind_floatnumber: case treeKind_decimalnumber: case treeKind_integernumber:
                        kind = treeKind_number;
                        break;
                    default :
                        {
                            if (((tree_trees[*resultTP].kind) > firstLeafKind) && ((tree_trees[*resultTP].kind) <= lastLeafKind)) {
                                kind = treeKind_id;  // anything not number
                            } else {
                                assert (false);
                            }
                        }
                        break;
                }

                if (kind == treeKind_number) {
                    double N1 = stringreal((*ident_idents[tree_trees[*resultTP].name]));
                    double N2 = stringreal((*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name]));

                    switch (ruleIndex) {
                        case greaterR:
                            *matched = N1 > N2;
                            break;
                        case greaterEqualR:
                            *matched = N1 >= N2;
                            break;
                        case lessR:
                            *matched = N1 < N2;
                            break;
                        case lessEqualR:
                            *matched = N1 <= N2;
                            break;
                    }

                } else {
                    longstring *S1 = &predefs_LS1;
                    longstring *S2 = &predefs_LS2;
                    predefs_evaluateString (*ident_idents[tree_trees[*resultTP].name], 
                        tree_trees[*resultTP].kind, *S1);
                    predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                        tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *S2);

                    switch (ruleIndex) {
                        case greaterR:
                            *matched = lstringcmp (*S1, *S2) > 0;
                            break;
                        case greaterEqualR:
                            *matched = lstringcmp (*S1, *S2) >= 0;
                            break;
                        case lessR:
                            *matched = lstringcmp (*S1, *S2) < 0;
                            break;
                        case lessEqualR:
                            *matched = lstringcmp (*S1, *S2) <= 0;
                            break;
                    }
                }
            }
            break;

        case extractR:
        case shallowextractR:
            {
                // Repeat_X [^ Y] or Repeat_X [^/ Y]
                // generic extract from any scope 
                // The scope should be something of type [repeat X] for some X.
                // Extracts a sequence of all of the occurences of something of type [X] in the ruleEnvironment.valueTP,
                // and appends it to the scope.

                // can't fail since types are right
                *matched = true;

                tokenT repeatXT = tree_trees[*resultTP].name;
                assert (stringncmp (ident_idents[repeatXT], "repeat_0_", 9) == 0); 
                string *X = (string *) &(lstringchar (*ident_idents[repeatXT], 10));  // substring (10 .. *)
                tokenT XT = ident_lookup (*X);
                assert (XT != NOT_FOUND);

                treePT extractTP;
                const bool recursive = ruleIndex == extractR;
                tree_extract (XT, repeatXT, transformer_valueTP[ruleEnvironment->valuesBase + 1], 
                    ((ruleEnvironment->parentrefs) != 1), recursive, &(extractTP));

                // hopefully it is a repeat_0_X tree we are attaching 
                assert (tree_trees[extractTP].name == repeatXT); 

                // now attach the tail to the scope repeat 
                treePT scopeTP = *resultTP;
                assert (tree_trees[scopeTP].name == repeatXT); 

                // attaching to a repeat_0_X - it may be empty! 
                if (tree_trees[tree_kid1TP (scopeTP)].kind == treeKind_empty) {
                    // empty repeat - replace it with the nonempty tail 
                    tree_setKids (scopeTP, tree_trees[extractTP].kidsKP);
                    return;
                }

                // we are now attaching to a nonempty repeat 
                assert (tree_trees[scopeTP].name == repeatXT); 

                // find last "repeat_0_" tree 
                while (true) {
                    if (tree_trees[tree_kid2TP (scopeTP)].kind == treeKind_empty) break;
                    scopeTP = tree_kid2TP (scopeTP);
                }
                tree_setKids (scopeTP, tree_trees[extractTP].kidsKP);
            }
            break;

        case substituteR:
            {
                // Scope [$ Old New]
                // generic substitute any type in any scope 

                // can't fail since types are right
                *matched = true;

                if (tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind >= firstLiteralKind) {
                    tree_substituteLiteral (transformer_valueTP[ruleEnvironment->valuesBase + 1], 
                        transformer_valueTP[ruleEnvironment->valuesBase + 2], &*resultTP);
                } else {
                    tree_substitute (transformer_valueTP[ruleEnvironment->valuesBase + 1], 
                        transformer_valueTP[ruleEnvironment->valuesBase + 2], &*resultTP);
                }
            }
            break;

        case newidR:
            {
                // Id [!]
                // make any identifier unique

                // can't fail since types are right
                *matched = true;

                treePT oldresultTP = *resultTP;
                *resultTP = tree_newTreeClone (oldresultTP);

                string *symbol = (string *) &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[*resultTP].name], treeKind_id, *symbol);

                assert ((stringchar (*symbol, 1) < '0') || (stringchar (*symbol, 1) > '9'));
                int nondigit = stringlen (*symbol);
                while (true) {
                    if (!(charset_digitP[(unsigned char) lstringchar (*symbol, nondigit)])) break;
                    nondigit -= 1;
                }
                substring (*symbol, *symbol, 1, nondigit);

                if ((nondigit + 6) > maxLineLength) {
                    nondigit = maxLineLength - 6;
                    substring (*symbol, *symbol, 1, nondigit);
                }

                string *newidnumber = (string *) &predefs_LS2;

                for (int i = 1; i <= 999999; i++) {
                    intstring (i, 1, 10, *newidnumber);
                    stringcat (*symbol, *newidnumber);
                    if (ident_lookup (*symbol) == NOT_FOUND) break;
                    substring (*symbol, *symbol, 1, nondigit);
                }

                tokenT resultT = ident_install (*symbol, treeKind_id);
                tree_setName (*resultTP, resultT);
                tree_setRawName (*resultTP, resultT);
            }
            break;

        case underscoreR:
            {
                // Id [_ Id2]
                // concat ids with _ between

                // can't fail since types are right
                *matched = true;

                treePT oldresultTP = *resultTP;
                *resultTP = tree_newTreeClone (oldresultTP);

                // Concatenate the raw names in case they are different
                string *oldId = (string *) (ident_idents[tree_trees[*resultTP].rawname]);
                string *parmId = 
                    (string *) (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].rawname]);
                string *newId = (string *) &predefs_LS1;
                stringcpy (*newId, *oldId);

                stringcat (*newId, "_");
                stringcat (*newId, *parmId);

                tokenT resultT = ident_install (*newId, treeKind_id);
                tree_setRawName (*resultTP, resultT);

                // If -case is on, normalize the internal name, otherwise it is the same
                if (options_option[case_p]) {
                    lstringtolower (*newId);
                    resultT = ident_install (*newId, treeKind_id);
                    tree_setName (*resultTP, resultT);
                } else {
                    tree_setName (*resultTP, resultT);
                }
            }
            break;

        case messageR:
            {
                // Print a message
                // Any1 [message Any2]
                if ((tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind == treeKind_stringlit) 
                        || (tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind == treeKind_charlit)) {
                    longstring *param1text = &predefs_LS1;
                    predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                        tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *param1text);
                    fprintf (stderr, "%s\n", *param1text);
                } else {
                    unparser_printLeaves (transformer_valueTP[ruleEnvironment->valuesBase + 1], 0, true);
                }
                *matched = false;
            }
            break;

        case printR:
            {
                // Print the leaves of the scope tree
                // Any [print]
                if ((tree_trees[*resultTP].kind == treeKind_stringlit) || (tree_trees[*resultTP].kind == treeKind_charlit)) {
                    longstring *resultext = &predefs_LS1;
                    predefs_evaluateString (*ident_idents[tree_trees[*resultTP].name], 
                        tree_trees[*resultTP].kind, *resultext);
                    fprintf (stderr, "%s\n", *resultext);
                } else {
                    unparser_printLeaves (*resultTP, 0, true);
                }
                *matched = false;
            }
            break;

        case printattrR:
            {
                // Print the leaves of the scope tree with attributes
                // Any [printattr]
                if ((tree_trees[*resultTP].kind == treeKind_stringlit) || (tree_trees[*resultTP].kind == treeKind_charlit)) {
                    longstring *resultext = &predefs_LS1;
                    predefs_evaluateString (*ident_idents[tree_trees[*resultTP].name], 
                        tree_trees[*resultTP].kind, *resultext);
                    fprintf (stderr, "%s\n", *resultext);
                } else {
                    bool saveattr = options_option[attr_p];
                    options_option[attr_p] = true;
                    unparser_printLeaves (*resultTP, 0, true);
                    options_option[attr_p] = saveattr;
                }
                *matched = false;
            }
            break;

        case debugR:
            {
                // Print the scope tree as a tree
                // Any [debug]
                fprintf (stderr, "\n");
                fprintf (stderr, "--- DEBUG %s ---\n", (*ident_idents[tree_trees[*resultTP].name]));
                unparser_printParse (*resultTP, 0, 0);
                fprintf (stderr, "\n");
                *matched = false;
            }
            break;

        case breakpointR:
            {
                // Breakpoint - stop and wait to continue
                // ANY [breakpoint]
                fprintf (stderr, "\n");
                fprintf (stderr, "--- BREAKPOINT [Hit return to continue] ");
                longstring *dummy = &predefs_LS1;
                tfgetlongstring (*dummy, tfstdin);
                *matched = false;
            }
            break;

        case quoteR: case unparseR:
            {
                longstring *leavestext = &predefs_LS1;
                unparser_quoteLeaves (transformer_valueTP[ruleEnvironment->valuesBase + 1], *leavestext);
                *resultTP = tree_newTreeClone (originalTP);

                longstring *resultext = &predefs_LS2;
                predefs_evaluateString (*ident_idents[tree_trees[*resultTP].name], 
                    tree_trees[*resultTP].kind, *resultext);
                lstringcat (*resultext, *leavestext);
                predefs_unevaluateString (tree_trees[*resultTP].kind, *resultext);

                tokenT resultT = ident_install (*resultext, tree_trees[*resultTP].kind);
                tree_setName (*resultTP, resultT);
                tree_setRawName (*resultTP, resultT);
                *matched = true;
            }
            break;

        case unquoteR:
            {
                // Replace id or comment with unquoted text of string or char literal
                // Id [unquote String]
                *resultTP = tree_newTreeClone (originalTP);
                longstring *parameterText = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *parameterText);
                tokenT resultT = ident_install (*parameterText, treeKind_id);
                tree_setName (*resultTP, resultT);
                tree_setRawName (*resultTP, resultT);
                *matched = true;
            }
            break;

        case parseR:
            {
                // Replace scope of type [X] with a parse of the text of string S as an [X]
                // Any [parse String]

                // Make sure we have room to scan and parse
                predefs_checkSufficientParseSpace ();

                // Get the text to parse from the parameter
                longstring *parameterText = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *parameterText);

                // Set up the tokens to parse by scanning the input
                scanner_tokenize (*parameterText, false, false);

                // Find the grammar type of the scope
                tokenT typename_ = tree_trees[*resultTP].name;

                if ((tree_trees[*resultTP].kind) >= firstLiteralKind) {
                    if (tree_trees[*resultTP].kind == treeKind_literal) {
                        predefs_predefinedError (
                            "Scope of [parse] function is a literal (possible cause: scope is [token], use [repeat token] instead)",
                            transformer_applyingRuleName, transformer_callingRuleName);
                    }
                    typename_ = tree_literalTypeName (tree_trees[*resultTP].kind);
                }

                int typeIndex = symbol_lookupSymbol (typename_);
                assert (typeIndex != NOT_FOUND);

                // Now attempt to parse them as the scope type
                treePT parseTreeTP = nilTree;
                parser_initializeParse ("[parse]", false, false, false, (struct ruleLocalsT *) 0, (* (parser_parseVarOrExpProc *) 0));
                parser_parse (symbol_symbols[typeIndex], &(parseTreeTP));

                // Make sure we got a parse
                if (parseTreeTP == nilTree) {
                    predefinedParseError (failTokenIndex, transformer_applyingRuleName, transformer_callingRuleName, typename_, 
                        *ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name]);
                }

                // Got one!
                *resultTP = parseTreeTP;
                *matched = true;
            }
            break;

        case reparseR:
            {
                // Replace scope of type [X] with a parse of the tokens (leaves) of the parameter tree as an [X]
                // Any1 [reparse Any2]

                // Make sure we have room to scan and parse
                predefs_checkSufficientParseSpace ();

                // Set up the tokens to parse from the leaves of the parameter
                unparser_extractLeaves (transformer_valueTP[ruleEnvironment->valuesBase + 1]);

                // Find the grammar type of the scope
                tokenT typename_ = tree_trees[*resultTP].name;

                if (tree_trees[*resultTP].kind >= firstLiteralKind) {
                    typename_ = tree_literalTypeName (tree_trees[*resultTP].kind);
                }

                int typeIndex = symbol_lookupSymbol (typename_);
                assert (typeIndex != NOT_FOUND);

                // Now attempt to parse them as the scope type
                treePT parseTreeTP = nilTree;
                parser_initializeParse ("[parse]", false, false, false, (struct ruleLocalsT *) 0, (* (parser_parseVarOrExpProc *) 0));
                parser_parse (symbol_symbols[typeIndex], &(parseTreeTP));

                // Make sure we got a parse
                if (parseTreeTP == nilTree) {
                    predefinedParseError (failTokenIndex, transformer_applyingRuleName, transformer_callingRuleName, typename_, 
                        *ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name]);
                }

                // Got one!
                *resultTP = parseTreeTP;
                *matched = true;
            }
            break;

        case readR:
            {
                // Replace scope of type [X] with a parse of parameter file as an [X].
                // Any [read Stringlit]

                // Make sure we have room to scan and parse
                predefs_checkSufficientParseSpace ();

                // Set up the tokens to parse by scanning the file
                longstring *fileName = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *fileName);
                lsubstring (*fileName, *fileName, 1, maxStringLength);

                scanner_tokenize (*fileName, true, false);

                // Find the grammar type of the scope
                tokenT typename_ = tree_trees[*resultTP].name;

                if (tree_trees[*resultTP].kind >= firstLiteralKind) {
                    typename_ = tree_literalTypeName (tree_trees[*resultTP].kind);
                }

                int typeIndex = symbol_lookupSymbol (typename_);
                assert (typeIndex != NOT_FOUND);

                // Now attempt to parse them as the scope type
                treePT parseTreeTP = nilTree;
                string *context = (string *) &predefs_LS2;
                stringprintf (*context, "[read] of file '%s'", *fileName);
                parser_initializeParse (*context, false, false, false, (struct ruleLocalsT *) 0, (* (parser_parseVarOrExpProc *) 0));
                parser_parse (symbol_symbols[typeIndex], &(parseTreeTP));

                // Make sure tht we got a parse
                if (parseTreeTP == nilTree) {
                    stringprintf (*context, "file '%s'", *fileName);
                    predefinedParseError (failTokenIndex, transformer_applyingRuleName, transformer_callingRuleName, typename_, *context);
                }

                // Got one!
                *resultTP = parseTreeTP;
                *matched = true;
            }
            break;

        case writeR:
            {
                // Output the leaves of the scope tree to a file
                // Any [write Stringlit]

                // Try to open the file
                longstring *fileName = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *fileName);
                lsubstring (*fileName, *fileName, 1, maxStringLength);

                int outstream = 0;
                tfopen (OPEN_CHAR_WRITE, *fileName, &outstream);

                if (outstream == 0) {
                    string *message = (string *) &predefs_LS3;
                    stringprintf (*message, "Unable to open output file '%s'", *fileName);
                    predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                }

                unparser_printLeaves (*resultTP, outstream, true);
                *matched = true;

                tfclose (outstream);
            }
            break;

        case getR: case getpR:
            {
                // Replace scope of type [X] with a parse of one line of terminal input as an [X]
                // (The parameter to [getp] is the prompt)
                // Any [get]
                // Any [getp Stringlit]

                // Make sure we have room to scan and parse
                predefs_checkSufficientParseSpace ();

                if (ruleIndex == getpR) {
                    // Issue the prompt
                    if ((tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind == treeKind_stringlit) 
                            || (tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind == treeKind_charlit)) {
                        longstring *promptext = &predefs_LS1;
                        predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                            tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *promptext);
                        fprintf (stderr, "%s", *promptext);
                    } else {
                        fprintf (stderr, "%s", (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name]));
                    }
                }

                treePT parseTreeTP = nilTree;
                while (true) {
                    // Get the input line
                    longstring *input = &predefs_LS1;
                    tfgetlongstring (*input, tfstdin);

                    // Set up the tokens to parse by scanning the input
                    scanner_tokenize (*input, false, false);

                    // Find the grammar type of the scope
                    tokenT typename_ = tree_trees[*resultTP].name;

                    if (tree_trees[*resultTP].kind >= firstLiteralKind) {
                        typename_ = tree_literalTypeName (tree_trees[*resultTP].kind);
                    }

                    int typeIndex = symbol_lookupSymbol (typename_);
                    assert (typeIndex != NOT_FOUND);

                    // Now attempt to parse them as the scope type
                    parser_initializeParse ("", false, false, false, (struct ruleLocalsT *) 0, (* (parser_parseVarOrExpProc *) 0));
                    parser_parse (symbol_symbols[typeIndex], &(parseTreeTP));

                    // Make sure that we got a parse
                    if (parseTreeTP != nilTree) break;

                    longstring *exttypename = &predefs_LS2;
                    externalType (*ident_idents[typename_], *exttypename);
                    fprintf (stderr, "[%s] input expected - try again (y/n)? ", *exttypename);
                    tfgetlongstring (*input, tfstdin);

                    if (stringcmp (*input, "y") != 0) {
                        predefinedParseError (failTokenIndex, transformer_applyingRuleName, transformer_callingRuleName, typename_, "input");
                    }
                }

                // Got one!
                *resultTP = parseTreeTP;
                *matched = true;
            }
            break;

        case putR: case putsR:
            {
                // Output the leaves of the scope tree to the terminal
                // Any [put]
                // S1 [puts]
                if ((tree_trees[*resultTP].kind == treeKind_stringlit) || (tree_trees[*resultTP].kind == treeKind_charlit)) {
                    longstring *resultext = &predefs_LS1;
                    predefs_evaluateString (*ident_idents[tree_trees[*resultTP].name], 
                        tree_trees[*resultTP].kind, *resultext);
                    fprintf (stderr, "%s\n", *resultext);
                } else {
                    unparser_printLeaves (*resultTP, 0, true);
                }
                *matched = false;
            }
            break;

        case putpR:
            {
                // The parameter is a pattern of the form "here it is % there it went"
                // where the "%" marks the point at which to insert the output, much like printf()
                // Any [putp Stringlit]

                // Output the first part of the pattern
                longstring *promptext = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *promptext);

                int pcindex = stringindex (*promptext, "%");
                if (pcindex != 0) {
                    longstring *pthead = &predefs_LS2;
                    lsubstring (*pthead, *promptext, 1, (pcindex - 1));
                    fprintf (stderr, "%s", *pthead);
                } else {
                    fprintf (stderr, "%s", *promptext);
                }

                // Output the scope
                if ((tree_trees[*resultTP].kind == treeKind_stringlit) || (tree_trees[*resultTP].kind == treeKind_charlit)) {
                    longstring *resultext = &predefs_LS3;
                    predefs_evaluateString (*ident_idents[tree_trees[*resultTP].name], 
                        tree_trees[*resultTP].kind, *resultext);
                    fprintf (stderr, "%s", *resultext);
                } else {
                    unparser_printLeaves (*resultTP, 0, false);
                }

                // Output the tail of the pattern
                if (pcindex != 0) {
                    longstring *pttail = &predefs_LS3;
                    lsubstring (*pttail, *promptext, (pcindex + 1), lstringlen (*promptext));
                    fprintf (stderr, "%s\n", *pttail);
                } else {
                    fprintf (stderr, "\n");
                }

                *matched = false;
            }
            break;

        case indexR:
            {
                // Replaces a number with the index of the first instance of S2 in S1, or zero if none is found
                // Number [index String1 String2]

                // Can't fail now ...
                longstring *param1text = &predefs_LS1;
                longstring *param2text = &predefs_LS2;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *param1text);
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 2]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 2]].kind, *param2text);

                int ix = lstringindex (*param1text, *param2text);

                *resultTP = tree_newTreeClone (originalTP);
                longstring *ixstring = &predefs_LS3;
                intstring (ix, 1, 10, *ixstring);
                tokenT resultT = ident_install (*ixstring, treeKind_number);
                tree_setName (*resultTP, resultT);
                tree_setRawName (*resultTP, resultT);
                *matched = true;
            }
            break;

        case grepR:
            {
                // Succeeds iff S2 is a substring of S1
                // String1 [grep String2]
                longstring *resultext = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[*resultTP].name], 
                    tree_trees[*resultTP].kind, *resultext);
                longstring *parameterText = &predefs_LS2;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *parameterText);

                *matched = lstringindex (*resultext, *parameterText) != 0;
            }
            break;

        case repeatlengthR:
            {
                // Replaces a number with the number of elements in the [repeat X] or [list X] parameter 
                // Number [length RepeatX]

                treePT paramTP = transformer_valueTP[ruleEnvironment->valuesBase + 1];

                // find the first element of the repeat
                if (tree_trees[tree_kid1TP (paramTP)].kind == treeKind_empty) {
                    // an empty repeat - return 0
                    *resultTP = tree_newTreeClone (originalTP);
                    tokenT resultT = ident_install ("0", treeKind_number);
                    tree_setName (*resultTP, resultT);
                    tree_setRawName (*resultTP, resultT);
                    return;
                }

                // nonempty repeat - compute the length
                int paramLength = 0;
                while (true) {
                    if (tree_trees[tree_kid2TP (paramTP)].kind == treeKind_empty) break;
                    paramLength += 1;
                    // go on to next "repeat_0_" tree 
                    paramTP = tree_kid2TP (paramTP);
                }

                // and return it
                *resultTP = tree_newTreeClone (originalTP);
                longstring *paramLengthString = &predefs_LS1;
                stringprintf (*paramLengthString, "%d", paramLength);
                tokenT resultT = ident_install (*paramLengthString, treeKind_number);
                tree_setName (*resultTP, resultT);
                tree_setRawName (*resultTP, resultT);
                *matched = true;
            }
            break;

        case selectR: case headR: case tailR:
            {
                // Replaces a [repeat X] or [list X] sequence with the subsequence from N1..N2 
                // RepeatX [select Number1 Number2]
                // RepeatX [head Number]                        == RepeatX [select 1 Number]
                // RepeatX [tail Number]                        == RepeatX [select Number *]

                int i1 = round (stringreal((*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name])));
                int i2 = i1;

                if (ruleIndex == selectR) {
                    i2 = round (stringreal((*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 2]].name])));
                } else if (ruleIndex == tailR) {
                    i2 = 999999;
                } else if (ruleIndex == headR) {
                    i1 = 1;
                }

                // make the functions total
                if (i1 < 1) {
                    i1 = 1;
                }

                if (i2 < (i1 - 1)) {
                    i2 = i1 - 1;
                }

                // repeat_0_X
                if (i2 < i1) {
                    // the result is empty
                    tree_makeTwoKids (*resultTP, emptyTP, emptyTP);
                    *matched = true;
                    return;
                }

                assert (i2 >= i1);
                
                // find the i1'th [repeat X] node
                int i = 1;
                treePT s1TP = *resultTP;
                while (true) {
                    if (tree_trees[tree_kid1TP (s1TP)].kind == treeKind_empty) {
                        // first index greater than length - make functions total by making result subsequence empty
                        tree_makeTwoKids (*resultTP, emptyTP, emptyTP);
                        *matched = true;
                        return;
                    }

                    if (i == i1) break;

                    // go on to the next "repeat_0_" tree
                    s1TP = tree_kid2TP (s1TP);
                    i += 1;
                }

                // now we have the i1'th [repeat X] tree in s1TP
                // find the i2'th one
                treePT s2TP = s1TP;
                while (true) {
                    // if i2 > length of repeat, means entire tail
                    if (tree_trees[tree_kid1TP (s2TP)].kind == treeKind_empty) break;

                    // otherwise end at i2
                    if (i == i2) break;

                    // go on to the next "repeat_0_" tree
                    s2TP = tree_kid2TP (s2TP);
                    i += 1;
                }

                // made it - now cut off the tail of s2TP
                if (tree_trees[tree_kid2TP (s2TP)].kind != treeKind_empty) {
                    s2TP = tree_kid2TP (s2TP);
                    tree_makeTwoKids (s2TP, emptyTP, emptyTP);
                }

                // and change scope to the result
                *resultTP = s1TP;
                *matched = true;
            }
            break;

        case quitR:
            {
                int exitCode = round (stringreal((*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name])));
                throw (exitCode);
            }
            break;

        case fgetR:
            {
                // Replace scope of type [X] with a parse of one line of file input as an [X]
                // Any [fget Filename]

                // Make sure we have room to scan and parse
                predefs_checkSufficientParseSpace ();

                longstring *fileName = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *fileName);
                lsubstring (*fileName, *fileName, 1, maxStringLength);

                // Is the file open?
                int fs = 0;
                int ff = 0;
                for (int f = 1; f <= maxFgetputFiles; f++) {
                    if (predefs_fgetputFiles[f].name ==
                            tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name) {
                        fs = predefs_fgetputFiles[f].stream;
                    } else if (predefs_fgetputFiles[f].name == NOT_FOUND) {
                        ff = f;
                    }
                }

                if (fs == 0) {
                    // It's not open - try to open it
                    string *message = (string *) &predefs_LS3;

                    if (ff == 0) {
                        stringprintf (*message, "Unable to open input file '%s' (too many open files)", *fileName);
                        predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                    }

                    tfopen (OPEN_CHAR_READ, *fileName, &fs);

                    if (fs == 0) {
                        stringprintf (*message, "Unable to open input file '%s'", *fileName);
                        predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                    }
                    predefs_fgetputFiles[ff].name = 
                        tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name;
                    predefs_fgetputFiles[ff].stream = fs;
                }

                treePT parseTreeTP = nilTree;

                // get the input line
                longstring *input = &predefs_LS2;
                tfgetlongstring (*input, fs);

                // Set up the tokens to parse by scanning the input
                scanner_tokenize (*input, false, false);

                // Find the grammar type of the scope
                tokenT typename_ = tree_trees[*resultTP].name;

                if (tree_trees[*resultTP].kind >= firstLiteralKind) {
                    typename_ = tree_literalTypeName (tree_trees[*resultTP].kind);
                }

                int typeIndex = symbol_lookupSymbol (typename_);
                assert (typeIndex != NOT_FOUND);

                // Now attempt to parse them as the scope type
                parseTreeTP = nilTree;
                parser_initializeParse ("", false, false, false, (struct ruleLocalsT *) 0, (* (parser_parseVarOrExpProc *) 0));
                parser_parse (symbol_symbols[typeIndex], &(parseTreeTP));

                // Make sure that we got a parse
                if (parseTreeTP != nilTree) {
                    string *context = (string *) &predefs_LS3;
                    stringprintf (*context, "file '%s'", *fileName);
                    predefinedParseError (failTokenIndex, transformer_applyingRuleName, transformer_callingRuleName, typename_, *context);
                }

                // Got one!
                *resultTP = parseTreeTP;
                *matched = true;
            }
            break;

        case fputR: case fputsR:
            {
                // Output the leaves of the scope tree to the line-oriented file
                // Any [fput Filename]
                // S1 [fputs Filename]

                // Check that the file is open
                longstring *fileName = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *fileName);
                lsubstring (*fileName, *fileName, 1, maxStringLength);

                // Is the file open?
                int fs = 0;
                int ff = 0;
                for (int f = 1; f <= maxFgetputFiles; f++) {
                    if (predefs_fgetputFiles[f].name ==
                            tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name) {
                        fs = predefs_fgetputFiles[f].stream;
                    } else if (predefs_fgetputFiles[f].name == NOT_FOUND) {
                        ff = f;
                    }
                }

                if (fs == 0) {
                    // It's not open - try to open it
                    string *message = (string *) &predefs_LS3;

                    if (ff == 0) {
                        stringprintf (*message, "Unable to open output file '%s' (too many open files)", *fileName);
                        predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                    }

                    tfopen (OPEN_CHAR_WRITE, *fileName, &fs);

                    if (fs == 0) {
                        stringprintf (*message, "Unable to open output file '%s'", *fileName);
                        predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                    }

                    predefs_fgetputFiles[ff].name = 
                        tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name;
                    predefs_fgetputFiles[ff].stream = fs;
                }

                // Output the scope
                if ((tree_trees[*resultTP].kind == treeKind_stringlit) || (tree_trees[*resultTP].kind == treeKind_charlit)) {
                    longstring *resultext = &predefs_LS2;
                    predefs_evaluateString (*ident_idents[tree_trees[*resultTP].name], 
                        tree_trees[*resultTP].kind, *resultext);
                    fprintf (tffile (fs), "%s\n", *resultext);
                } else {
                    unparser_printLeaves (*resultTP, fs, true);
                }

                // Flush all output streams to keep synchronous
                tfflush ();

                *matched = false;
            }
            break;

        case fputpR:
            {
                // The second parameter is a pattern of the form "here it is % there it went"
                // where the "%" marks the point at which put the output
                // Any [fputp Filename Stringlit]

                // Check that the file is open
                longstring *fileName = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *fileName);
                lsubstring (*fileName, *fileName, 1, maxStringLength);

                // Is the file open?
                int fs = 0;
                int ff = 0;
                for (int f = 1; f <= maxFgetputFiles; f++) {
                    if (predefs_fgetputFiles[f].name ==
                            tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name) {
                        fs = predefs_fgetputFiles[f].stream;
                    } else if (predefs_fgetputFiles[f].name == NOT_FOUND) {
                        ff = f;
                    }
                }

                if (fs == 0) {
                    // It's not open - try to open it
                    string *message = (string *) &predefs_LS3;

                    if (ff == 0) {
                        stringprintf (*message, "Unable to open output file '%s' (too many open files)", *fileName);
                        predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                    }

                    tfopen (OPEN_CHAR_WRITE, *fileName, &fs);

                    if (fs == 0) {
                        stringprintf (*message, "Unable to open output file '%s'", *fileName);
                        predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                    }
                    predefs_fgetputFiles[ff].name = 
                        tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name;
                    predefs_fgetputFiles[ff].stream = fs;
                }

                // Output the first part of the pattern
                longstring *promptext = &predefs_LS2;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 2]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 2]].kind, *promptext);

                int pcindex = stringindex (*promptext, "%");
                if (pcindex != 0) {
                    longstring *pthead = &predefs_LS3;
                    lsubstring (*pthead, *promptext, 1, (pcindex - 1));
                    fprintf (tffile (fs), "%s", *pthead);
                } else {
                    fprintf (tffile (fs), "%s", *promptext);
                }

                // Output the scope
                if ((tree_trees[*resultTP].kind == treeKind_stringlit) || (tree_trees[*resultTP].kind == treeKind_charlit)) {
                    longstring *resultext = &predefs_LS2;
                    predefs_evaluateString (*ident_idents[tree_trees[*resultTP].name], 
                        tree_trees[*resultTP].kind, *resultext);
                    fprintf (tffile (fs), "%s", *resultext);
                } else {
                    unparser_printLeaves (*resultTP, fs, false);
                }

                // Output the tail of the pattern
                if (pcindex != 0) {
                    longstring *pttail = &predefs_LS3;
                    lsubstring (*pttail, *promptext, (pcindex + 1), lstringlen (*promptext));
                    fprintf (tffile (fs), "%s\n", *pttail);
                } else {
                    fprintf (tffile (fs), "\n");
                }

                // Flush all output streams to keep synchronous
                tfflush ();

                *matched = false;
            }
            break;

        case fopenR:
            {
                // Explicitly open a line-oriented input/output file
                // Any [fopen Filename Method], where Method = in[put]/get, out[put]/put or mod/append

                // Get filename
                longstring *fileName = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *fileName);
                lsubstring (*fileName, *fileName, 1, maxStringLength);

                // Get method (in/get, out/put, mod/app)
                longstring *method = &predefs_LS2;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 2]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 2]].kind, *method);
                lsubstring (*method, *method, 1, maxStringLength);

                // Is there a slot for the file?
                int fs = 0;
                int ff = 0;
                for (int f = 1; f <= maxFgetputFiles; f++) {
                    if (predefs_fgetputFiles[f].name ==
                            tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name) {
                        fs = predefs_fgetputFiles[f].stream;
                    } else if (predefs_fgetputFiles[f].name == NOT_FOUND) {
                        ff = f;
                    }
                }

                string *message = (string *) &predefs_LS3;

                if (fs != 0) {
                    // It's already open - oops!
                    stringprintf (*message, "Unable to open file '%s' (already open)", *fileName);
                    predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);

                } else {
                    // Try to open the file
                    if (ff == 0) {
                        stringprintf (*message, "Unable to open file '%s' (too many open files)", *fileName);
                        predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                    }

                    // What kind of open do we need?  in[put]/get, out[put]/put or mod/append
                    if ((stringncmp (*method, "in", 2) == 0) || (stringncmp (*method, "get", 3) == 0)) {
                        tfopen (OPEN_CHAR_READ, *fileName, &fs);
                    } else if ((stringncmp (*method, "out", 3) == 0) || (stringncmp (*method, "put", 3) == 0)) {
                        tfopen (OPEN_CHAR_WRITE, *fileName, &fs);
                    } else if ((stringncmp (*method, "mod", 3) == 0) || (stringncmp (*method, "app", 3) == 0)) {
                        tfopen (OPEN_CHAR_APPEND, *fileName, &fs);
                    } else {
                        stringprintf (*message, "Unknown open mode '%s' (must be one of get/input, put/output, mod/append)", *method);
                        predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                    }

                    if (fs == 0) {
                        stringprintf (*message, "Unable to open file '%s'", *fileName);
                        predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                    } else if ((stringncmp (*method, "mod", 3) == 0) || (stringncmp (*method, "app", 3) == 0)) {
                        tfseekend (fs);
                    }

                    predefs_fgetputFiles[ff].name = 
                        tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name;
                    predefs_fgetputFiles[ff].stream = fs;
                }

                *matched = false;
            }
            break;

        case fcloseR:
            {
                // Close a line-oriented input/output file
                // Any [fclose Filename]

                // Check that the file is open
                longstring *fileName = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *fileName);
                lsubstring (*fileName, *fileName, 1, maxStringLength);

                int fs = 0;
                int ff = 0;
                for (int f = 1; f <= maxFgetputFiles; f++) {
                    if (predefs_fgetputFiles[f].name == 
                            tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name) {
                        fs = predefs_fgetputFiles[f].stream;
                        ff = f;
                    }
                }

                if (fs != 0) {
                    tfclose (predefs_fgetputFiles[ff].stream);
                    predefs_fgetputFiles[ff].name = 0;
                    predefs_fgetputFiles[ff].stream = 0;
                } else {
                    string *message = (string *) &predefs_LS3;
                    stringprintf (*message, "Unable to close file '%s' (file not open)", *fileName);
                    predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                }

                *matched = false;
            }
            break;

        case faccessR:
            {
                // Test whether a file can be opened in the given mode
                // Any [faccess Filename Method], where Method = in[put]/get, out[put]/put or mod/append

                // Get filename
                longstring *fileName = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *fileName);
                lsubstring (*fileName, *fileName, 1, maxStringLength);

                // Get method (in/get, out/put, mod/app)
                longstring *method = &predefs_LS2;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 2]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 2]].kind, *method);
                lsubstring (*method, *method, 1, maxStringLength);

                // Try to open in the specified mode: in[put]/get, out[put]/put or mod/append
                int fs = 0;
                if ((stringncmp (*method, "in", 2) == 0) || (stringncmp (*method, "get", 3) == 0)) {
                    tfopen (OPEN_CHAR_READ, *fileName, &fs);
                } else if ((stringncmp (*method, "out", 3) == 0) || (stringncmp (*method, "put", 3) == 0)) {
                    tfopen (OPEN_CHAR_WRITE, *fileName, &fs);
                } else if ((stringncmp (*method, "mod", 3) == 0) || (stringncmp (*method, "app", 3) == 0)) {
                    tfopen (OPEN_CHAR_APPEND, *fileName, &fs);
                } else {
                    string *message = (string *) &predefs_LS3;
                    stringprintf (*message, "Unknown access mode '%s' (must be one of get/input, put/output, mod/append)", *method);
                    predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                }

                // We succeed if it opened in the specified mode, fail otherwise
                if (fs != 0) {
                    tfclose (fs);
                    *matched = true;
                } else {
                    *matched = false;
                }
            }
            break;

        case pragmaR:
            {
                // Dynamically change TXL options
                // Any [pragma OptionsString]

                longstring *optionsString = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *optionsString);
                lsubstring (*optionsString, *optionsString, 1, maxStringLength);

                options_processOptionsString (*optionsString);

                *matched = false;
            }
            break;

        case systemR:
            {
                // Invoke system command (dangerous!)
                // Any [system CommandString]

                assert ((tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind == treeKind_stringlit) 
                     || (tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind == treeKind_charlit ));

                longstring *commandString = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *commandString);
                lsubstring (*commandString, *commandString, 1, maxStringLength);

                extern int system (const char *);
                int retcode = system (*commandString);

                // Possibly we should return the return code?
                if (retcode == 0) {
                    *matched = true;
                } else {
                    *matched = false;
                }
            }
            break;

        case pipeR:
            {
                // Invoke system command (dangerous!)
                // S1 [pipe CommandString]

                assert ((tree_trees[*resultTP].kind == treeKind_stringlit) 
                     || (tree_trees[*resultTP].kind == treeKind_charlit ));

                assert ((tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind == treeKind_stringlit) 
                     || (tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind == treeKind_charlit ));

                longstring *scopeString = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[*resultTP].name], 
                    tree_trees[*resultTP].kind, *scopeString);

                longstring *commandString = &predefs_LS2;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *commandString);
                lsubstring (*commandString, *commandString, 1, maxStringLength);

                // create command line
                longstring *systemString = &predefs_LS3;
                lstringcpy (*systemString, "echo '");
                lstringcat (*systemString, *scopeString);
                lstringcat (*systemString, "' | ");
                lstringcat (*systemString, *commandString);
                lstringcat (*systemString, " > _TXsRsL_");

                int retcode = system (*systemString);

                if (retcode == 0) {
                    *matched = true;
                } else {
                    *matched = false;
                }

                // get the result ...
                int f = 0;
                tfopen (OPEN_CHAR_READ, "_TXsRsL_", &f);

                if (f == 0) {
                    *matched = false;
                    lstringcpy (*scopeString, "");
                } else {
                    tfgetlongstring (*scopeString, f);
                    predefs_unevaluateString (tree_trees[*resultTP].kind, *scopeString);
                    tfclose (f);
                }

                // ... and return it
                treePT oldresultTP = *resultTP;
                *resultTP = tree_newTreeClone (oldresultTP);
                tokenT resultT = ident_install (*scopeString, tree_trees[*resultTP].kind);
                tree_setName (*resultTP, resultT);
                tree_setRawName (*resultTP, resultT);

                // Clean up the mess
                if (lstringcmp (directoryChar, "/") == 0) {
                    retcode = system ("/bin/rm -f _TXsRsL_");
                } else {
                    // Windows
                    retcode = system ("del _TXsRsL_");
                }
            }
            break;

        case tolowerR:
            {
                // Id [tolower]
                // make any token lower case

                // can't fail since types are right
                *matched = true;

                treePT oldresultTP = *resultTP;
                *resultTP = tree_newTreeClone (oldresultTP);

                longstring *tokentext = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[*resultTP].name], 
                    tree_trees[*resultTP].kind, *tokentext);
                lstringtolower (*tokentext);
                predefs_unevaluateString (tree_trees[*resultTP].kind, *tokentext);

                tokenT resultT = ident_install (*tokentext, tree_trees[*resultTP].kind);
                tree_setName (*resultTP, resultT);
                tree_setRawName (*resultTP, resultT);
            }
            break;

        case toupperR:
            {
                // Id [toupper]
                // make any token upper case

                // can't fail since types are right
                *matched = true;

                treePT oldresultTP = *resultTP;
                *resultTP = tree_newTreeClone (oldresultTP);

                longstring *tokentext = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[*resultTP].rawname], 
                    tree_trees[*resultTP].kind, *tokentext);
                lstringtoupper (*tokentext);
                predefs_unevaluateString (tree_trees[*resultTP].kind, *tokentext);

                tokenT resultT = ident_install (*tokentext, tree_trees[*resultTP].kind);
                tree_setRawName (*resultTP, resultT);

                // If -case is on, normalize the internal name, otherwise it is the same
                if ((options_option[case_p]) 
                        && (tree_trees[*resultTP].kind != treeKind_stringlit) && (tree_trees[*resultTP].kind != treeKind_charlit)) {
                    lstringtolower (*tokentext);
                    resultT = ident_install (*tokentext, tree_trees[*resultTP].kind);
                    tree_setName (*resultTP, resultT);
                } else {
                    tree_setName (*resultTP, resultT);
                }
            }
            break;

        case typeofR:
            {
                // I [typeof X]
                // Set I to type of tree X as an [id]

                *resultTP = tree_newTreeClone (originalTP);
                assert (tree_trees[*resultTP].kind == treeKind_id);

                if (tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind < firstLiteralKind) {
                    tree_setName (*resultTP, tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name);
                    tree_setRawName (*resultTP, tree_trees[*resultTP].name);
                } else {
                    tree_setName (*resultTP, kindType[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind]);
                    tree_setRawName (*resultTP, tree_trees[*resultTP].name);
                }

                *matched = true;
            }
            break;

        case istypeR:
            {
                // X [istype I]
                // Succeed iff the type of tree X as an [id] is I

                assert (tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind == treeKind_id);

                if (tree_trees[*resultTP].kind < firstLiteralKind) {
                    *matched = 
                        (tree_trees[*resultTP].name == tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name);
                } else {
                    *matched = (kindType[tree_trees[*resultTP].kind] == 
                        tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name);
                }
            }
            break;

/// TO HERE ///

        case roundR:
            {
                // N1 [round]
                // N1 := round (N1)

                // can't fail since types are ok
                *matched = true;

                treePT oldresultTP = *resultTP;
                *resultTP = tree_newTreeClone (oldresultTP);

                double N1 = stringreal((*ident_idents[tree_trees[*resultTP].name]));
                N1 = round (N1);

                longstring *resultValue = &predefs_LS1;
                predefs_convertRealToString (N1, *resultValue);
                tokenT resultT = ident_install (*resultValue, treeKind_number);
                tree_setName (*resultTP, resultT);
                tree_setRawName (*resultTP, resultT);
            }
            break;

        case truncR:
            {
                // N1 [trunc]
                // N1 := trunc (N1)

                // can't fail since types are ok
                *matched = true;

                treePT oldresultTP = *resultTP;
                *resultTP = tree_newTreeClone (oldresultTP);

                double N1 = stringreal((*ident_idents[tree_trees[*resultTP].name]));
                N1 = floor (N1);

                longstring *resultValue = &predefs_LS1;
                predefs_convertRealToString (N1, *resultValue);
                tokenT resultT = ident_install (*resultValue, treeKind_number);
                tree_setName (*resultTP, resultT);
                tree_setRawName (*resultTP, resultT);
            }
            break;

        case getsR:
            {
                // Replace scope of type [stringlit] or [charlit] with the text of one line of input 
                // S1 [gets]

                // Make sure we have room to scan and parse
                predefs_checkSufficientParseSpace ();

                // Get the input line
                longstring *input = &predefs_LS1;
                tfgetlongstring (*input, tfstdin);

                // And return it as string
                predefs_unevaluateString (tree_trees[*resultTP].kind, *input);

                treePT oldresultTP = *resultTP;
                *resultTP = tree_newTreeClone (oldresultTP);
                tokenT resultT = ident_install (*input, tree_trees[*resultTP].kind);
                tree_setName (*resultTP, resultT);
                tree_setRawName (*resultTP, resultT);

                *matched = true;
            }
            break;

        case fgetsR:
            {
                // Replace scope of type [stringlit] or [charlit] with the text of one line of file input 
                // S1 [fgets Filename]

                // Check that the file is open
                longstring *fileName = &predefs_LS1;
                predefs_evaluateString (*ident_idents[tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name], 
                    tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].kind, *fileName);
                lsubstring (*fileName, *fileName, 1, maxStringLength);

                // Is the file open?
                int fs = 0;
                int ff = 0;
                for (int f = 1; f <= maxFgetputFiles; f++) {
                    if (predefs_fgetputFiles[f].name ==
                            tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name) {
                        fs = predefs_fgetputFiles[f].stream;
                    } else if (predefs_fgetputFiles[f].name == NOT_FOUND) {
                        ff = f;
                    }
                }

                string *message = (string *) &predefs_LS3;

                if (fs == 0) {
                    // It's not open - try to open it
                    if (ff == 0) {
                        stringprintf (*message, "Unable to open input file '%s' (too many open files)", *fileName);
                        predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                    }

                    tfopen (OPEN_CHAR_READ, *fileName, &fs);

                    if (fs == 0) {
                        stringprintf (*message, "Unable to open input file '%s'", *fileName);
                        predefs_predefinedError (*message, transformer_applyingRuleName, transformer_callingRuleName);
                    }

                    predefs_fgetputFiles[ff].name = tree_trees[transformer_valueTP[ruleEnvironment->valuesBase + 1]].name;
                    predefs_fgetputFiles[ff].stream = fs;
                }

                // get the input line
                longstring *input = (longstring *) &predefs_LS2;
                tfgetlongstring (*input, fs);

                // And return it as a string
                predefs_unevaluateString (tree_trees[*resultTP].kind, *input);

                treePT oldresultTP = *resultTP;
                *resultTP = tree_newTreeClone (oldresultTP);
                tokenT resultT = ident_install (*input, tree_trees[*resultTP].kind);
                tree_setName (*resultTP, resultT);
                tree_setRawName (*resultTP, resultT);

                *matched = true;
            }
            break;
    }
}

// Initialization
void predefs_initializePredefs (void) {
    {
        // 1-origin [1 .. maxFgetputFiles]
        for (int f = 1; f <= maxFgetputFiles; f++) {
            predefs_fgetputFiles[f].name = NOT_FOUND;
            predefs_fgetputFiles[f].stream = 0;
        }
        predefs_fgetputFiles[0].name = UNUSED;
    }
}
